use std::ops::Deref;
use jwt_simple::prelude::*;

use crate::{AppError, User};

const JWT_DURATION: u64 = 60 * 60 * 24 * 1000;
// 提供商
const JWT_ISS: &'static str = "chat_server";
// 受众
const JWT_AUD: &'static str = "chat_client";

pub struct EncodingKey(Ed25519KeyPair);
pub struct DecodingKey(Ed25519PublicKey);

impl EncodingKey {
    pub fn load(pem: &str) -> Result<Self, AppError> {
        Ok(Self(Ed25519KeyPair::from_pem(pem)?))
    }
    pub fn encode(&self, user: User) -> Result<String, AppError> {
        let mut claims = Claims::with_custom_claims(user, Duration::from_secs(JWT_DURATION));
        let claims = claims.with_issuer(JWT_ISS)
            .with_audience(JWT_AUD);

        let string = self.sign(claims)?;

        Ok(string)
    }

}

impl DecodingKey {
    pub fn load(pem: &str) -> Result<Self, AppError> {
        Ok(Self(Ed25519PublicKey::from_pem(pem)?))
    }

    pub fn verify(&self, token: &str) -> Result<User, AppError> {
        let mut options = VerificationOptions::default();
        options.allowed_issuers = Some(HashSet::from_strings(&[JWT_ISS]));
        options.allowed_audiences = Some(HashSet::from_strings(&[JWT_AUD]));

        let claims = self.verify_token::<User>(token, Some(options))?;

        Ok(claims.custom)
    }
}
impl Deref for EncodingKey {
    type Target = Ed25519KeyPair;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Deref for DecodingKey {
    type Target = Ed25519PublicKey;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[cfg(test)]
mod tests {
    use crate::User;
    use crate::utils::jwt::{DecodingKey, EncodingKey};

    #[tokio::test]
    async fn test_jwt() -> anyhow::Result<()> {
        let encode_pem = include_str!("../../fixtures/private.pem");
        let decode_pem = include_str!("../../fixtures/decoding.pem");

        let user = User::new(100, "test-user", "cui@cui");

        let encode_key = EncodingKey::load(encode_pem)?;
        let decode_key = DecodingKey::load(decode_pem)?;

        let token = encode_key.encode(user.clone())?;
        let u = decode_key.verify(token.as_str())?;

        println!("id: {}, fullname: {}, email: {}", u.id, u.fullname, u.email);

        assert_eq!(user, u);
        Ok(())
    }
}